package cn.tedu._07springaop.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

import java.util.Arrays;

/**
 * @Aspect:标识此类为切面类
 * AOP（Aspect Oriented Programming，面向切面编程）是一种编程范式，
 * 用于将横切关注点（Cross-Cutting Concerns）从业务逻辑中分离出来，实现程序的模块化。
 * 以下是AOP的核心概念和特点：
 * 切面（Aspect）：
 * 切面是封装横切关注点（如日志记录、事务管理、安全检查等）的模块。它定义了在哪些 Join Point（连接点）上执行代码。
 * 连接点（Join Point）：
 * 程序执行过程中的某个特定点，如方法调用、异常抛出或字段访问等，可以被切面所拦截的位置。
 * 通知（Advice）：
 * 在切面中定义的具体操作，即在特定的连接点上执行的代码片段。常见的通知类型有前置通知（Before）、
 * 后置通知（After）、环绕通知（Around）等。
 * 切入点（Pointcut）：
 * 用于匹配连接点的一组规则，决定在哪些连接点上应用通知。通过表达式来描述切面应该在哪些Join Point上执行。
 * 织入（Weaving）：
 * 将切面代码插入到目标对象（即被代理的对象）的过程。这个过程可以在编译时、类加载时或运行时进行。
 * AOP的优势包括：
 * 解耦：通过将横切关注点与业务逻辑分离，提高了代码的可维护性和可重用性。
 * 集中管理：使得诸如日志记录这样的通用功能易于管理和修改，无需在每个需要的地方重复编写相同的代码。
 * 提高灵活性：修改切面逻辑不会影响到业务逻辑，反之亦然，便于应对需求变化。
 * 在Java中，Spring框架广泛支持AOP编程，提供了基于代理（JDK动态代理、CGLIB）的AOP实现方式。
 */
@Aspect
@Component
public class LogAspect {
    /**
     * Before注解:
     * 1. 标识此方法是一个前置通知
     * 2.调用目标方法之前执行的方法
     * 3. value属性:标识此方法是在哪个切入点执行
     * 4. execution属性:标识此方法是在哪个切入点执行
     */
//    @Before(value = "execution(public int cn.tedu._07springaop.aop.CalculatorImpl.*(..))")
//    public void before(JoinPoint joinPoint) {
//        String name = joinPoint.getSignature().getName();
//        String args = Arrays.toString(joinPoint.getArgs());
//        System.out.println("1.[前置通知]" + name + "方法开始执行，参数为：" + args);
//    }
    /**
     * 返回通知，在CalculatorImpl类中的所有公共int类型方法执行完成后调用。
     * 该注解指定了一个切点表达式，匹配CalculatorImpl类中所有公共int类型的方法。
     * @AfterReturning注解的returning属性指定了一个名为result的参数，该参数将包含方法的返回值。
     * @param joinPoint 切点，包含关于执行方法的信息，如方法名等。
     * @param result 方法的返回值，可以通过这个参数获取到方法执行后的结果。
     * 返回通知在有异常的情况下不会执行。
     */
//    @AfterReturning(value = "execution(public int cn.tedu._07springaop.aop.CalculatorImpl.*(..))", returning = "result")
//    public void afterReturning(JoinPoint joinPoint, Object result) {
//        String name = joinPoint.getSignature().getName();
//        System.out.println("2.[返回通知]" + name + "方法执行结束，结果为：" + result);
//    }
    /**
     * 后置通知函数，用于在目标方法执行完成后执行。
     * 该注解指定了这个方法是一个切面的后置通知，它会在匹配的方法执行完成后被调用。
     * 切面表达式"execution(public int cn.tedu._07springaop.aop.CalculatorImpl.*(..))"
     * 匹配了CalculatorImpl类中所有返回类型为int的公共方法。
     * 参数joinPoint用于获取方法执行的相关信息，如方法名等。
     * 该方法的主要作用是打印出方法执行结束的信息，用于日志记录或调试。
     * @param joinPoint 切点，包含关于执行方法的信息，如方法名等。
     */
//    @After(value = "execution(public int cn.tedu._07springaop.aop.CalculatorImpl.*(..))")
//    public void after(JoinPoint joinPoint) {
//        String name = joinPoint.getSignature().getName();
//        System.out.println("3.[后置通知]" + name + "方法执行结束");
//    }
    /**
     * 异常处理的切面逻辑。
     * 该方法在CalculatorImpl类中执行任何公共方法时发生异常时被调用。
     * 它的目的是捕获这些异常，并提供一个统一的异常处理机制。
     * @param joinPoint 切点，包含有关正在执行的方法的信息。用于获取方法名称。
     * @param ex 异常对象，包含发生的异常的具体信息。用于输出异常消息。
     */
//    @AfterThrowing(value = "execution(public int cn.tedu._07springaop.aop.CalculatorImpl.*(..))", throwing = "ex")
//    public void afterThrowing(JoinPoint joinPoint, Exception ex) {
//        String name = joinPoint.getSignature().getName();
//        System.out.println("4.[异常通知]" + name + "方法执行异常，异常信息为：" + ex.getMessage());
//    }
    /**
     * @Around:环绕通知
     * 1.环绕在目标方法的每个阶段都可以增强
     * 2.等价于：前置通知+返回通知+异常通知+后置通知
     * 3.通常使用try-catch-finally来处理异常
     */
    @Around(value = "execution(public int cn.tedu._07springaop.aop.CalculatorImpl.*(..))")
    public Object around(ProceedingJoinPoint joinPoint) {
        Object result = null;
        System.out.println("环绕通知:前置通知");
        try {
            //执行目标方法
            result = joinPoint.proceed();
            System.out.println("环绕通知:返回通知");
        } catch (Throwable e) {
            System.out.println("环绕通知:异常通知");
            throw new RuntimeException(e);
        } finally {
            System.out.println("环绕通知:后置通知");
        }
        return result;
    }

}
